package org.marverse.scripting

import ScriptingUtil._

sealed abstract case class UncheckedLambdaExpr(val argumentDeclaration: TupleType, val body: UncheckedExpression) 
extends UncheckedExpression {

}

object UncheckedLambdaExpr {
  def apply(argumentDeclaration: TupleType, body: UncheckedExpression): UncheckedLambdaExpr =
    new UncheckedLambdaExprImpl(argumentDeclaration, body)
  def apply(argumentDeclaration: VariableDeclaration, body: UncheckedExpression): UncheckedLambdaExpr =
    UncheckedLambdaExpr(TupleType(Seq(argumentDeclaration)), body)
  def apply(argumentDeclaration: Seq[VariableDeclaration], body: UncheckedExpression): UncheckedLambdaExpr =
    UncheckedLambdaExpr(TupleType(argumentDeclaration), body)
}

sealed abstract case class LambdaExpr(val argumentDeclaration: TupleType, val body: Expression) 
extends Expression {
  def expressionType(implicit ctx: MutableTypedMap[TypedKey, Any]): FunctionType
  def evaluate(implicit outerCtx: MutableTypedMap[TypedKey, Any]): Function
}

private class UncheckedLambdaExprImpl(argumentDeclaration: TupleType, body: UncheckedExpression) 
extends UncheckedLambdaExpr(argumentDeclaration, body) {

  override def childUncheckedExpressions = Seq(body)
  override def checked(implicit ctx: MutableTypedMap[TypedKey, Any]) = 
    new LambdaExprImpl(argumentDeclaration, body.checked)
  
}

private class LambdaExprImpl(argumentDeclaration: TupleType, body: Expression)
extends LambdaExpr(argumentDeclaration, body) {
  
  override def childExpressions = Seq(body)
  override def expressionType(implicit ctx: MutableTypedMap[TypedKey, Any]) = 
    FunctionType(argumentDeclaration, body.expressionType)    
  override def evaluate(implicit outerCtx: MutableTypedMap[TypedKey, Any]) = new Function() {
    // save a copy of ctx
    private implicit val savedCtx = outerCtx.clone
    override def apply(argument: Any)(implicit innerCtx: MutableTypedMap[TypedKey, Any]) = {
      // TODO: what to do with innerCtx? is it OK to ignore it?
      // argument is expected to be a tuple value
      assert(argumentDeclaration.isInstance(argument))
      // create a new context which received the variable bindings
      implicit val ctx2 = savedCtx.clone 
      // the arguments are bound to their respective variables in a new context 
      for((argVar, argVal) <- argumentDeclaration.componentTypes.elements zip argument.asInstanceOf[TupleValue].components.elements) { 
        (argVar := argVal)(ctx2)
      }
      // the body is evaluated under the new context with the bound variables
      body.evaluate(ctx2)
    }
  }
  
}
